/**
 * 
 */
package com.novartis.nims.trialsubmitter;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Properties;
import java.util.zip.GZIPOutputStream;

import javax.swing.SwingWorker;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.rsna.ctp.stdstages.anonymizer.AnonymizerStatus;
import org.rsna.ctp.stdstages.anonymizer.dicom.DicomAnonymizer;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.novartis.nims.util.RecursiveFileListIterator;
import com.novartis.nims.util.SHA1Hash;
import com.novartis.nims.util.SHA1HashException;
import com.pixelmed.dicom.Attribute;
import com.pixelmed.dicom.AttributeList;
import com.pixelmed.dicom.DicomException;
import com.pixelmed.dicom.DicomInputStream;
import com.pixelmed.dicom.TagFromName;

/**
 * @author snydejo1
 *
 */
public class TrialRegistrar extends SwingWorker<Void, Void> {

	static final Logger mLogger = Logger.getRootLogger();
	
	private Trial mTrial;
	
	private long mTrialSizeInBytes;
	private long mCurrentBytes = 0;
	
	private HashMap<String, Integer> mScanRepeatMap;
	private HashMap<String, String> mFileMap;

	private volatile StageValue mStage = StageValue.PENDING;
	
    /**
     * Values for the {@code stage} bound property.
     */
    public enum StageValue {
        /**
         * Initial state.
         */
        PENDING,
        
        /**
         * First stage is to calculate directory size. Used to determine progress for next stage.
         * Can take very long itself on large inputs.
         */
        CALCULATE_SIZE,

        /**
         * Second stage is to associate clinical trial keys with image files, and use these to
         * organize files into a DOM representing the clinical trial subjects, visits, and scans.
         */
        ASSOCIATE_KEYS,

        /**
         * Third stage is to clean (anonymize) the input image files, copy the cleaned versions
         * into the trial submission directory, and update the trial DOM to reference them.
         */
        CLEAN_IMAGES,

        /**
         * Final stage is to generate and output an ODM file representing the trial.
         */
        MAKE_ODM
    };
	
	/**
	 * 
	 */
	public TrialRegistrar(Trial trial) {
		mTrial = trial;
		
		mScanRepeatMap = new HashMap<String, Integer>();
		mFileMap = new HashMap<String, String>();
	}

	/* (non-Javadoc)
	 * @see org.jdesktop.swingworker.SwingWorker#doInBackground()
	 */
	@Override
	protected Void doInBackground() throws Exception {		
		if (!this.isCancelled()) {
			this.setStage(StageValue.CALCULATE_SIZE);
			mTrialSizeInBytes = this.calculateTrialSize();
			if (mTrialSizeInBytes < 1) {
				throw new TrialRegistrarException("Couldn't find any files in source directory. " +
						"Try checking the path to the subject directories, or the contents of the " +
						"subject directories.");
			}
			this.setProgress(0);
		}
		if (!this.isCancelled()) {
			this.setStage(StageValue.ASSOCIATE_KEYS);
			this.parseTrial(mTrial.getVisitsOrganized());
		}
		if (!this.isCancelled()) {
			this.setStage(StageValue.CLEAN_IMAGES);
			this.cleanTrial();
		}
		if (!this.isCancelled()) {
			this.setStage(StageValue.MAKE_ODM);
			this.makeODM();
		}
		return null;
	}

	private long calculateTrialSize() throws TrialRegistrarException {
		long totalBytes = 0;
		
		RecursiveFileListIterator fileIterator = new RecursiveFileListIterator(new File(mTrial.getSourceDirectory()));
		while (fileIterator.hasNext() && (!this.isCancelled())) {
			File file = fileIterator.next();
			totalBytes += file.length();
			
			// HACK can't calculate determinate progress, so keep updating with a
			// repeating iteration from 1 to 99
			this.setProgress(Math.max(1, Math.min(99, ((int) totalBytes % 100))));
		}
		
		if (totalBytes >= new File(mTrial.getSubmissionDirectory()).getUsableSpace()) {
			throw new TrialRegistrarException("Not enough space on partition holding submission " +
					"directory. Try using another partition or drive with at least " + totalBytes +
					" free.");
		}
		
		return totalBytes;
	}

	private void parseTrial(boolean visitsOrganized) throws TrialRegistrarException {
		Document document = mTrial.getDocument();
		Element root = document.getDocumentElement();
		File rootDirectory = new File(mTrial.getSourceDirectory());
		
		Element study = document.createElement("Study");
		// study ID is input directory name
		study.setAttribute("id", rootDirectory.getName());
		root.appendChild(study);
		
		// register each child directory as a new subject
		File[] subjects = rootDirectory.listFiles();

		if (subjects == null) {
			throw new TrialRegistrarException("Trial directory is unreadable " +
					"or is actually a file: " + rootDirectory.getAbsolutePath());
		}
		if (subjects.length < 1) {
			throw new TrialRegistrarException("Couldn't find any subject directories. Try checking the " +
					"source directory.");
		}
		
		for (int subjectIndex = 0; subjectIndex < subjects.length; subjectIndex++) {
			File subjectFile = subjects[subjectIndex];
			String subjectString = subjectFile.getName();
			
			Element subjectNode = document.createElement("Subject");
			subjectNode.setAttribute("id", subjectString);
			
			boolean childrenAdded = false;
			
			if (visitsOrganized) {
				File[] visits = subjectFile.listFiles();
				
				if (visits == null) {
					throw new TrialRegistrarException("Subject directory is " +
							"unreadable or is actually a file: " + 
							subjectFile.getAbsolutePath());
				}
				if (visits.length < 1) {
					throw new TrialRegistrarException("Subject directory is empty. " +
							"Skipping: " + subjectFile.getAbsolutePath());
				}
				
				for (int visitIndex = 0; visitIndex < visits.length; visitIndex++) {
					File visitFile = visits[visitIndex];
					String visitString = visitFile.getName();
					
					if (visitString.equals(mTrial.getUnscheduledVisitIdentifier())) {
						File [] unscheduleds = visitFile.listFiles();

						if (unscheduleds == null) {
							throw new TrialRegistrarException("Visit directory is " +
									"unreadable or is actually a file: " +
									visitFile.getAbsolutePath());
						}
						if (unscheduleds.length < 1) {
							throw new TrialRegistrarException("Visit directory is " +
									"empty. Skipping: " +
									visitFile.getAbsolutePath());
						}
						
						for (int unscheduledIndex = 0; unscheduledIndex < unscheduleds.length; unscheduledIndex++) {
							File unscheduledFile = unscheduleds[unscheduledIndex];
							String unscheduledString = unscheduledFile.getName();
							
							Element visitNode = document.createElement("Visit");
							visitNode.setAttribute("id", visitString);
							visitNode.setAttribute("repeatkey", unscheduledString);
							subjectNode.appendChild(visitNode);

							if (addFileChildrenToTrial(visitsOrganized, unscheduledFile, visitNode)) {
								childrenAdded = true;
							}
						}
						
					} else { // end if unscheduled
						Element visitNode = document.createElement("Visit");
						visitNode.setAttribute("id", visitString);
						visitNode.setAttribute("repeatkey", "1");
						subjectNode.appendChild(visitNode);
						
						if (addFileChildrenToTrial(visitsOrganized, visitFile, visitNode)) {
							childrenAdded = true;
						}
					} // if unscheduled else
					
				} // for each visit
				
			} else { //end if visitsOrganized
				if (addFileChildrenToTrial(visitsOrganized, subjectFile, subjectNode)) {
					childrenAdded = true;
				}
			} // if (visitsOrganized) else
			
			if (childrenAdded) {
				study.appendChild(subjectNode);
			}
		}
	}
	
	private boolean addFileChildrenToTrial(boolean visitsOrganized, File parentFile, Node parentNode) throws TrialRegistrarException {
		RecursiveFileListIterator fileIterator = new RecursiveFileListIterator(parentFile);
		
		if (!fileIterator.hasNext()) {
			throw new TrialRegistrarException("Input directory structure not as " +
					"expected. Check the Help menu. Problem " +
					"file/directory: " + parentFile.getAbsolutePath());
		}
		
		mScanRepeatMap = new HashMap<String, Integer>();
		boolean childrenAdded = false;
		
		while (fileIterator.hasNext()) {
			if (this.isCancelled()) {
				return false;
			}
			File file = fileIterator.next();
			
			if (file.length() < (mTrial.getFileMinimumKB() * 1024)) {
				mLogger.warn("File is less than minimum length specified. " +
						"Skipping: " +
						file.getAbsolutePath());
				continue;
			}
			
			if (visitsOrganized) {
				if (addFileToVisit(file, parentNode)) {
					childrenAdded = true;
				}
			} else {
				if (addFileToSubject(file, parentNode)) {
					childrenAdded = true;
				}
			}
		}
		
		return childrenAdded;
	}
	
	private void abortOrWarn(boolean abort, String message) throws TrialRegistrarException {
		if (abort) {
			throw new TrialRegistrarException(message);
		} else {
			mLogger.warn(message);
		}
	}
	
	private boolean addFileToVisit(File file, Node visitNode) throws TrialRegistrarException {
		AttributeList list = new AttributeList();
		DicomInputStream dis = null;
		try {
			dis = new DicomInputStream(file);
			try {
				list.read(dis);
			} catch (DicomException e) {
				abortOrWarn(mTrial.getAbortOnUnspecifiedFileError(), file.getAbsolutePath() +
						" has an unspecified DICOM problem.");
				return false;
			} finally {
				if (dis != null) dis.close();
			}
		} catch (IOException e) {
			abortOrWarn(mTrial.getAbortOnUnspecifiedFileError(), file.getAbsolutePath() +
					" couldn't be read because of IO error.");
			return false;
		}		
		
		//Attribute attr = list.get(TagFromName.SeriesInstanceUID);
		Attribute attr = list.get(mTrial.getScanKey());
		if (attr == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingScanKey(), file.getAbsolutePath() +
					" missing DICOM attribute " + mTrial.getScanKey().toString() + ".");
			return false;
		}

		
		String scanKey = attr.getSingleStringValueOrNull();
		if (scanKey == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingScanKey(), file.getAbsolutePath() +
					" has empty DICOM attribute " + mTrial.getScanKey().toString() + ".");
			return false;
		}

		//modality
		attr = list.get(mTrial.getScanName());
		if (attr == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingScanName(), file.getAbsolutePath() +
					" missing DICOM attribute " + mTrial.getScanName().toString() + ".");
			return false;
		}
		
		
		String scanName = attr.getSingleStringValueOrNull();
		if (scanName == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingScanName(), file.getAbsolutePath() +
					" has empty DICOM attribute " + mTrial.getScanName().toString() + ".");
			return false;
		}
		
		//sopinstanceuid
		attr = list.get(mTrial.getFileKey());
		if (attr == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingFileKey(), file.getAbsolutePath() +
					" missing DICOM attribute " + mTrial.getFileKey().toString() + ".");
			return false;
		}
				
		
		String fileKey = attr.getSingleStringValueOrNull();
		if (fileKey == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingFileKey(), file.getAbsolutePath() +
					" has empty DICOM attribute " + mTrial.getFileKey().toString() + ".");
			return false;
		}
		

		String previousFileName = mFileMap.put(fileKey, file.getAbsolutePath());
		if (previousFileName != null) {
			abortOrWarn(mTrial.getAbortOnFileWithDuplicateFileKey(), file.getAbsolutePath() +
					" contains duplicate SOP Instance UID " + fileKey +	" already found in file " +
					previousFileName);
			return false;
		}

		Element scanNode = null;
		
		NodeList scans = visitNode.getChildNodes();
		for (int i = 0; (i < scans.getLength()) && (scanNode == null); i++) {
			Element currentElement = (Element) scans.item(i);
			String key = currentElement.getAttribute("dicomKey");
			if (key.equals(scanKey)) {
				scanNode = currentElement;
			}
		}

		Document document = mTrial.getDocument();
		if (scanNode == null) {
			scanNode = document.createElement("Scan");
			scanNode.setAttribute("id", scanName);
			scanNode.setAttribute("dicomKey", scanKey);
			
			Integer scanRepeat = mScanRepeatMap.get(scanName);
			String scanRepeatString = null;
			Integer newRepeatVal = new Integer(1);
			if (scanRepeat == null) {
				scanRepeatString = "1";
			} else {
				newRepeatVal = scanRepeat + 1;
				scanRepeatString = newRepeatVal.toString();
			}
			mScanRepeatMap.put(scanName, newRepeatVal);
			scanNode.setAttribute("repeatkey", scanRepeatString);
			visitNode.appendChild(scanNode);
		}
		
		// current method is private, so use is tightly controlled: only called on unique
		// files per instance. if access changes, update to throw an exception on repeated
		// file key
		
		// filesystem coded metadata for DOM
		int pathOffset = new File(mTrial.getSourceDirectory()).getParentFile().getAbsolutePath().length();
		String fileName = file.getAbsolutePath().substring(pathOffset);
		
		Element fileNode = document.createElement("ImageFile");
		fileNode.setAttribute("name", fileName);
		fileNode.setAttribute("sha1hash", "");
		scanNode.appendChild(fileNode);

		// ensure progress never runs past 100
		mCurrentBytes += file.length();
		this.setProgress((int) Math.min(100, 100 * mCurrentBytes / mTrialSizeInBytes));
		
		return true;
	}
	
	private boolean addFileToSubject(File file, Node subjectNode) throws TrialRegistrarException {
		AttributeList list = new AttributeList();
		DicomInputStream dis = null;
		try {
			dis = new DicomInputStream(file);
			try {
				list.read(dis);
			} catch (DicomException e) {
				abortOrWarn(mTrial.getAbortOnUnspecifiedFileError(), file.getAbsolutePath() +
					" has an unspecified DICOM problem.");
				return false;
			} finally {
				if (dis != null) dis.close();
			}
		} catch (IOException e) {
			abortOrWarn(mTrial.getAbortOnUnspecifiedFileError(), file.getAbsolutePath() +
				" couldn't be read because of IO error.");
			return false;
		}

		
		// DICOM attribute values used to populate required metadata in the DOM model
		// reject file if any are missing or empty.
		
		//acquisitiondate
		Attribute attr = list.get(mTrial.getVisitKey());
		if (attr == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingVisitKey(), file.getAbsolutePath() +
					" missing DICOM attribute " + mTrial.getVisitKey().toString() + ".");
			return false;
		}
		
		String visitKey = attr.getSingleStringValueOrNull();
		if (visitKey == null) {
			abortOrWarn(mTrial.getAbortOnFileMissingVisitKey(), file.getAbsolutePath() +
					" has empty DICOM attribute " + mTrial.getVisitKey().toString() + ".");
			return false;
		}
		
		
		// hardcoded metadata for DOM
		String visitName = visitKey;
		String visitRepeat = "1";
		
		Element visitNode = null;
		
		NodeList visits = subjectNode.getChildNodes();
		for (int i = 0; (i < visits.getLength()) && (visitNode == null); i++) {
			Element currentElement = (Element) visits.item(i);
			String key = currentElement.getAttribute("dicomKey");
			if (key.equals(visitKey)) {
				visitNode = currentElement;
			}
		}

		Document document = mTrial.getDocument();
		if (visitNode == null) {
			visitNode = document.createElement("Visit");
			visitNode.setAttribute("id", visitName);
			visitNode.setAttribute("dicomKey", visitKey);
			visitNode.setAttribute("repeatkey", visitRepeat);
		}
		
		if (this.addFileToVisit(file, visitNode)) {
			subjectNode.appendChild(visitNode);
			return true;
		} else {
			return false;
		}
		
	}

	private void cleanTrial() throws TrialRegistrarException {
		mLogger.info("Started cleaning images.");
		
		String inDir = mTrial.getSourceDirectory();
		String outDir = mTrial.getSubmissionDirectory();
		
		/** Clean all image files: anonymization and hashing **/
		
		// generate mirc properties file describing anonymization rules from bundled NIMS
		// specification
		String mircFileName = outDir + File.separator + "generic.mirc.properties";
		StreamSource nimsRules =
			new StreamSource(ApplicationConfiguration.getResourceAsStream("generic.decs.xml"));
		
		StreamSource nimsToMircXsl =
			new StreamSource(ApplicationConfiguration.getResourceAsStream("decs2mirc.xslt"));


		TransformerFactory tf = TransformerFactory.newInstance();
		FileOutputStream outStream = null;
		try {
			outStream = new FileOutputStream(mircFileName);
			try {
				StreamResult streamResult = new StreamResult(outStream);
				Transformer t = tf.newTransformer(nimsToMircXsl);
				t.transform(nimsRules, streamResult);
			} catch (TransformerException e) {
				throw new TrialRegistrarException("Internal problem generating anonymization rules. " +
						"The application was probably configured and deployed incorrectly.", e);
			} finally {
				if (outStream != null) outStream.close();
			}
		} catch (FileNotFoundException e) {
			throw new TrialRegistrarException("Could not write file to output directory. " +
					"Make sure the output directory exists and is writeable.", e);
		} catch (IOException e) {
			throw new TrialRegistrarException("Unspecified IO error. Try using a different " +
					"output directory.", e);
		}

		
		// retrieve all image file name nodes from DOM tree
		//String xPathImageFiles = "/submission/Study/Subject/Visit/Scan/ImageFile";
		String xPathImageFiles = "//ImageFile";
		XPath xPath = XPathFactory.newInstance().newXPath();
		NodeList imageFileNodes;
		try {
			imageFileNodes = (NodeList)xPath.evaluate(xPathImageFiles,
					mTrial.getDocument(),
					XPathConstants.NODESET);
		} catch (XPathExpressionException e) {
			throw new TrialRegistrarException("Couldn't find any subjects or images in source directory. " +
					"Try checking the contents of the input subject directories.", e);
		}
		

		int numberOfNodes = imageFileNodes.getLength();
		if (numberOfNodes < 1) {
			throw new TrialRegistrarException("Couldn't find any images in source directory. Try checking " +
					"the contents of the input subject directories.");
		}		

		// clean each file
		for (int i = 0; (i < numberOfNodes) && (!this.isCancelled()); i++) {
			if (this.isCancelled()) {
				return;
			}
			
			Element imageFileElement = (Element)imageFileNodes.item(i);
			String fileRelativePath = imageFileElement.getAttribute("name");
			File inputFile = new File(new File(inDir).getParent() + File.separator + fileRelativePath);


			mLogger.info("Cleaning " + inputFile.getAbsolutePath());
			
			//re-organize file directory structure to match trial structure
			StringBuilder newFileRelativePath = new StringBuilder(60);
			for (
					Node node = imageFileElement.getParentNode();
					node.getNodeName() != "submission";
					node = node.getParentNode()) {
				
				newFileRelativePath.insert(0, "/");
				String repeatkey = ((Element) node).getAttribute("repeatkey");
				if (!repeatkey.isEmpty()) {
					newFileRelativePath.insert(0, repeatkey);
					newFileRelativePath.insert(0, "/");
				}
				newFileRelativePath.insert(0, ((Element) node).getAttribute("id"));
			}

			// use a temporary output file
			File anonOutFile = new File(outDir + File.separator + "anon-out.tmp");
			
			//anonymize could be an expensive operation. check if canceled first.
			if (this.isCancelled()) {
				return;
			}

			// anonymize
			boolean doAnonymize = true;
			if (doAnonymize) {
				try {
					anonymize(inputFile, anonOutFile, mircFileName);
				} catch (TrialRegistrarException e) {
					if (mTrial.getAbortOnUnspecifiedFileError()) {
						throw e;
					} else {
						mLogger.warn(e.getMessage());
						anonOutFile.delete();
						continue;
					}
				}
			} else {
				try {
					this.copyFile(inputFile, anonOutFile);
				} catch (IOException e) {
					throw new TrialRegistrarException("Couldn't copy file to output directory: " +
							inputFile.getAbsolutePath() + ". Make sure output directory wasn't changed " +
							"or removed.", e);
				}
			}
			
			// hash could also be expensive, return if cancelled during anonymize
			if (this.isCancelled()) {
				anonOutFile.delete();
				return;
			}
			// hash value of anonymized output file used in trial model and as a default file name
			String hash = null;
			FileInputStream hashStream = null;
			try {
				hashStream = new FileInputStream(anonOutFile);
			} catch (FileNotFoundException e) {
				throw new TrialRegistrarException("Couldn't create temporary file for writing: " +
						anonOutFile.getAbsolutePath() + ". Make sure output directory wasn't changed " +
						"or removed.", e);
			}
			
			try {
				try {
					hash = SHA1Hash.hash(hashStream);
				} finally {
					// SHA1HashException could be swallowed if this throws an exception
					hashStream.close();
				}
			} catch (SHA1HashException e) {
				throw new TrialRegistrarException("Couldn't generate a SHA1 hash for file " +
						anonOutFile.getAbsolutePath() + " (generated from input file " +
						inputFile.getAbsolutePath() + "). Try checking permissions on directories " +
						"and make sure you're using the recommended JRE.", e);
			} catch (IOException e) {
				// thrown by hashStream.close()
				throw new TrialRegistrarException("Couldn't generate a SHA1 hash for file " +
						anonOutFile.getAbsolutePath() + " (generated from input file " +
						inputFile.getAbsolutePath() + "). Try checking permissions on directories.",
						e);
			}

			
			// rename anonymized output file using SOP Instance UID
			DicomInputStream dis = null;
			AttributeList list = new AttributeList();
			try {
				try {
					dis = new DicomInputStream(anonOutFile);
					list.read(dis);
				} catch (DicomException e) {
					throw new TrialRegistrarException("Couldn't read anonymized SOP Instance UID for " +
							anonOutFile.getAbsolutePath() + " (generated from " +
							inputFile.getAbsolutePath() + ").", e);
				} finally {
					// if IOException was thrown, it could be swallowed if this also throws an exception
					dis.close();
				}
			} catch (IOException e) {
				throw new TrialRegistrarException("Couldn't read anonymized SOP Instance UID for " +
						anonOutFile.getAbsolutePath() + " (generated from " +
						inputFile.getAbsolutePath() + ").", e);
			}
			
			Attribute attr = list.get(TagFromName.SOPInstanceUID);
			if (attr == null) {
				throw new TrialRegistrarException(inputFile.getAbsolutePath() +
						" is missing the SOP Instance UID attribute after being anonymized. Try " +
						"checking the input file or the anonymization configuration.");
			}
			
			StringBuilder uniqueFileName = new StringBuilder(120);
			String sopInstanceUID = attr.getSingleStringValueOrNull();
			if (sopInstanceUID == null) {
				throw new TrialRegistrarException(inputFile.getAbsolutePath() +
						" has no value for the SOP Instance UID attribute after being anonymized. Try " +
						"checking the input file or the anonymization configuration.");
			} else {
				uniqueFileName.append(newFileRelativePath);
				uniqueFileName.append(sopInstanceUID);
				uniqueFileName.append(".dcm");
			}

//			// rename anonymized output file using hash
//			StringBuilder uniqueFileName = new StringBuilder(120);
//			uniqueFileName.append(newFileRelativePath);
//			uniqueFileName.append(hash);
//			uniqueFileName.append(".dcm");	
			
			File cleanedOutFile = new File(outDir + File.separator + uniqueFileName);
			
			mLogger.info("Saving cleaned input (" + inputFile.getAbsolutePath() + ") to output (" +
					cleanedOutFile.getAbsolutePath() + ").");
			
			if (cleanedOutFile.exists()) {
				throw new TrialRegistrarException(cleanedOutFile.getAbsolutePath() +
						" already exists. Another file from this scan had the same SOP Instance UID. Try checking files in " +
						inputFile.getParentFile().getAbsolutePath() + " for duplicates.");
			}

			cleanedOutFile.getParentFile().mkdirs();
			if (!anonOutFile.renameTo(cleanedOutFile)) {
				throw new TrialRegistrarException("Couldn't move temporary file " +
						anonOutFile.getAbsolutePath() + " to cleaned submission file " +
						cleanedOutFile.getAbsolutePath() + ".");
			}


			imageFileElement.setAttribute("sha1hash", hash);
			imageFileElement.setAttribute("name", uniqueFileName.toString());
			
			this.setProgress(100 * i / numberOfNodes);
		}
		mLogger.info("Done cleaning images.");
	}
	
	private void copyFile(File in, File out) throws IOException	{
		FileChannel inChannel = new	FileInputStream(in).getChannel();
		FileChannel outChannel = new FileOutputStream(out).getChannel();
		try {
			inChannel.transferTo(0, inChannel.size(), outChannel);
		}
		finally {
			if (inChannel != null) inChannel.close();
			if (outChannel != null) outChannel.close();
		}
	}

	private void anonymize(
			File inFile,
			File outFile,
			String dicomScriptFileName) throws TrialRegistrarException {
		
		// Load the anonymizer script
		Properties dicomScript = new Properties();
		
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(dicomScriptFileName);
			dicomScript.load(fis);
		} catch (IOException e) {
			//programmer error
			throw new RuntimeException("Inteneral error running anonymization. " +
					"Try containing the application provider.", e);
		} finally {
			if (fis != null) try { fis.close(); } catch (IOException ignore) { }
		}
		
		// perform the anonymization
		AnonymizerStatus result = DicomAnonymizer.anonymize(
				inFile, outFile,
				dicomScript, null,
				false, false);
		
		// DicomAnonymizer.anonymize creates a temp directory that it never deletes.
		// We delete it here.
		File tempDir = new File(outFile.getParentFile().getParentFile(),
				"anonymizer-temp");
		if (tempDir.exists()) {
			tempDir.delete();
		}

		if (!result.isOK()) {
			throw new TrialRegistrarException(
					inFile.getAbsolutePath() +
					" could not be anonymized. Try checking anonymization profile configuration," +
					" or fixing or discarding the input file.");
		}
	}
	
	private void makeODM() throws TransformerException, IOException {
		GZIPOutputStream zip = new GZIPOutputStream(new FileOutputStream(
				new File(mTrial.getSubmissionDirectory() + File.separator + "odm.xml.gz")));
		
		InputStream xslStream = ApplicationConfiguration.getResourceAsStream("ImageFileTree2ODM.xslt");
		StreamSource xslSource = new StreamSource(xslStream);
		DOMSource domSource = new DOMSource(mTrial.getDocument());
		StreamResult streamResult = new StreamResult(zip);
		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer t = tf.newTransformer(xslSource);
		t.transform(domSource, streamResult);
		zip.close();
	}

	public StageValue getStage() {
		return mStage;
	}

	public void setStage(StageValue stage) {
		StageValue oldStage = mStage;
		mStage = stage;
		this.firePropertyChange("stage", oldStage, mStage);
	}

}
